package com.zeta.demo.es.controller;

import cn.hutool.core.bean.BeanUtil;
import com.zeta.demo.es.common.param.EsPageParam;
import com.zeta.demo.es.model.document.PostDocument;
import com.zeta.demo.es.model.entity.Post;
import com.zeta.demo.es.model.enums.SearchTypeEnum;
import com.zeta.demo.es.model.param.PostSaveDTO;
import com.zeta.demo.es.model.param.PostUpdateDTO;
import com.zeta.demo.es.model.query.PostQueryDTO;
import com.zeta.demo.es.service.IPostService;
import com.zeta.demo.es.service.es.IPostEsService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.zetaframework.annotation.log.SysLog;
import org.zetaframework.annotation.satoken.PreAuth;
import org.zetaframework.base.exception.ArgumentException;
import org.zetaframework.base.result.ApiResult;
import org.zetaframework.base.result.PageResult;
import org.zetaframework.controller.SuperSimpleController;
import org.zetaframework.controller.curd.DeleteController;
import org.zetaframework.controller.curd.SaveController;
import org.zetaframework.controller.curd.UpdateController;

import java.util.List;
import java.util.Set;

/**
 * 文章管理
 *
 * @author gcc
 */
@Validated
@Tag(name = "文章管理", description = "文章管理")
@PreAuth(enabled = false)
@RestController
@RequestMapping("/demo/es/post")
@RequiredArgsConstructor
public class PostController extends SuperSimpleController<IPostService, Post>
    implements SaveController<Post, PostSaveDTO>,
    UpdateController<Post, PostUpdateDTO>,
    DeleteController<Post, Long>
{

    private final IPostEsService postEsService;

    /**
     * 分页查询
     *
     * @param param 分页参数
     */
    @SysLog(response = false)
    @Operation(summary = "分页查询")
    @PostMapping("/page")
    public ApiResult<PageResult<PostDocument>> page(@RequestBody EsPageParam<PostQueryDTO> param) {
        return success(postEsService.page(param));
    }

    /**
     * 根据id查询
     *
     * @param id Id
     */
    @SysLog
    @Operation(summary = "根据id查询")
    @GetMapping("/{id}")
    public ApiResult<PostDocument> get(@Parameter @PathVariable Long id) {
        return success(postEsService.getById(id));
    }

    /**
     * 根据标题查询
     *
     * 注解地狱啊，注解地狱😵
     *
     * @param title 文章标题
     * @param type 搜索类型 0：分词查询  1：精确查询
     */
    @SysLog
    @Operation(summary = "根据标题查询")
    @GetMapping("/title")
    public ApiResult<List<PostDocument>> findByTitle(
            @Parameter(description = "文章标题")
            @NotBlank(message = "标题内容不能为空")
            @Size(max = 60, message = "文章标题长度不能超过60")
            @RequestParam
            String title,
            @Parameter(description = "搜索类型", example = "0", required = false)
            @RequestParam(defaultValue = "0", required = false)
            Integer type
    ) {
        SearchTypeEnum typeEnum = SearchTypeEnum.getByCode(type);
        return switch (typeEnum) {
            case WORD_SEARCH -> success(postEsService.searchByTitle(title));
            case EXACT_SEARCH -> success(postEsService.searchByTitleKeyword(title));
            default -> throw new ArgumentException("搜索类型参数值异常");
        };
    }


    /**
     * 高亮搜索
     *
     * @param content 文章内容
     */
    @SysLog(response = false)
    @Operation(summary = "高亮搜索")
    @GetMapping("/highlight")
    public ApiResult<List<String>> highlightSearch(
            @Parameter(description = "文章内容")
            @NotBlank(message = "内容不能为空")
            @Size(min = 1, max = 30, message = "标题长度不能超过30个字符")
            @RequestParam String content
    ) {
        return success(postEsService.searchByContentWithHighLight(content));
    }

    /**
     * 自定义新增
     * <p>
     * 说明:
     * <pre>
     * 同步保存方案：
     * 考虑到本demo只是简单个人博客，不存在并发量很大的情况，因此采用同步保存的方式。
     * 这种做法的优势在于：
     *   当数据库或ES保存任一环节出现问题时，事务能够自动回滚，保证了数据的强一致性
     *
     * 异步保存方案:
     * 如果对数据一致性要求不是特别严格，可以采用以下异步保存方案：
     * 1. 定时任务同步
     *   先保存数据到数据库，然后通过定时任务扫描数据表，定期将新增或更新的数据同步到ES中‘
     * 2. 消息队列处理
     *   将数据发送到消息队列，由消费者监听并处理ES同步。
     *     - 利用消息队列的失败重试机制提高可靠性
     *     - 通过死信队列处理异常情况
     *     - 实现数据的最终一致性
     * 3. 消息表机制
     *   创建专用的消息表用于数据同步：
     *     - 将待同步数据另存一份到消息表中，状态设置为"未处理"
     *     - 发送异步消息通知消费者处理
     *     - 消费者处理完成后将状态更新为"已处理"
     *     - 支持失败重试和状态跟踪
     * </pre>
     *
     * @param saveDTO SaveDTO 保存对象
     * @return ApiResult<Boolean>
     */
    @Override
    public ApiResult<Boolean> handlerSave(PostSaveDTO saveDTO) {
        Post entity = BeanUtil.toBean(saveDTO, Post.class);
        // 保存到数据库 和 es 中
        if (!service.customSave(entity)) {
            return fail("操作失败", false);
        }
        return success(true);
    }

    /**
     * 自定义修改
     *
     * @param updateDTO UpdateDTO 修改对象
     * @return ApiResult<Boolean>
     */
    @Override
    public ApiResult<Boolean> handlerUpdate(PostUpdateDTO updateDTO) {
        Post entity = service.getById(updateDTO.getId());
        if (entity == null) { return success(true); }

        // 重要：保证要修改的entity所有字段都有值。 ps: es的修改是全量修改。想要修改部分字段比较繁琐，这里偷懒了
        BeanUtil.copyProperties(updateDTO, entity);

        // 修改数据库 和 es 中的数据
        if (!service.customUpdate(entity)) {
            return fail("操作失败", false);
        }
        return success(true);
    }

    /**
     * 自定义单体删除
     *
     * @param id Id
     * @return ApiResult<Boolean>
     */
    @Override
    public ApiResult<Boolean> handlerDelete(Long id) {
        Post entity = service.getById(id);
        if (entity == null) { return success(true); }

        // 删除数据库 和 es 中的数据
        if (!service.customDeleteById(id)) {
            return fail("操作失败", false);
        }
        return success(true);
    }


    /**
     * 自定义批量删除
     *
     * @param ids List<Id>
     * @return ApiResult<Boolean>
     */
    @Override
    public ApiResult<Boolean> handlerBatchDelete(Set<Long> ids) {
        // 获取批量删除的实体
        List<Post> entityList = service.listByIds(ids);
        if (entityList.isEmpty()) { return success(true); }

        // 提取id
        List<Long> batchIds = entityList.stream().map(Post::getId).toList();

        // 批量删除数据库 和 es 中的数据
        if (!service.customBatchDeleteById(batchIds)) {
            return fail("操作失败", false);
        }
        return success(true);
    }
}
